/**
 * 
 */
package group.anglers.om.core.persistence.serialization.impl;

import group.anglers.om.core.annotations.Column;
import group.anglers.om.core.annotations.ColumnPK;
import group.anglers.om.core.annotations.CompositeKey;
import group.anglers.om.core.annotations.Model;
import group.anglers.om.core.commons.StringUtils;
import group.anglers.om.core.exceptions.ModelException;
import group.anglers.om.core.exceptions.OmException;
import group.anglers.om.core.exceptions.UnExpectedException;
import group.anglers.om.core.persistence.serialization.Composite;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @author an.pham
 *
 */
public class ModelSerializationUtil {
	private static final String METHOD_GETTER = "get";
	private static final String METHOD_SETTER = "set";
	/**
	 * 
	 * @param modelClass
	 * @return
	 * @throws ModelException
	 */
	public static ModelSerialization serialize(Class<?> modelClass) throws ModelException {
		/* get mapped table name */
		Model model = modelClass.getAnnotation(Model.class);
		
		if(model == null){
			throw new ModelException("\"" + modelClass.getName() + "\": is not a Model !!!");
		}
		
		ModelSerializationImpl modelSerializationImpl = new ModelSerializationImpl();
		
		modelSerializationImpl.setModelClass(modelClass);
		modelSerializationImpl.setTableMapping(model.mapping());
		
		Class<?> classField = null;
		Field[] compositeClassFields = null;
		/* get mapped table fields */
		Field[] modelFields = modelClass.getDeclaredFields();
		/* iterator fields of model */
		for(Field modelField : modelFields){
			// primary key
			classField = modelField.getType();
			if (modelField.isAnnotationPresent(ColumnPK.class)) {
				//check duplicate primary key field
				if(modelSerializationImpl.getPrimaryKey() != null){
					throw new ModelException("\"" + modelClass.getName() + "\": only allowed to have one primary key !!!");
				}
				// composite primary key
				if (classField.isAnnotationPresent(CompositeKey.class)) {
					CompositeImpl compositePK = new CompositeImpl(modelField.getName(), modelField.getType());
					
					modelSerializationImpl.setPrimaryKey(compositePK);
					
					compositeClassFields = classField.getDeclaredFields();
					/* iterator fields of composite field */
					for (Field compositeClassField : compositeClassFields) {
						if (compositeClassField.isAnnotationPresent(Column.class)) {
							//check primitive type
							if(compositeClassField.getType().isPrimitive()){
								throw new ModelException("\"" + modelClass.getName() 
										+ "\": "
										+ classField.getName() + "'s "
										+  modelField.getName() 
										+ " is not supported primitive type yet !!!");
							}
							
							compositePK.addColumn(new ColumnImpl(compositeClassField.getName(), compositeClassField.getType()));
						}
					}
				}// normal primary key
				else {
					//check primitive type
					if(classField.isPrimitive()){
						throw new ModelException("\"" + modelClass.getName() 
								+ "\": " +  modelField.getName() 
								+ " is not supported primitive type yet !!!");
					}
					modelSerializationImpl.setPrimaryKey(new ColumnImpl(modelField.getName(), modelField.getType()));
				}
			}
			// normal field
			else if (modelField.isAnnotationPresent(Column.class)) {
				//check primitive type
				if(classField.isPrimitive()){
					throw new ModelException("\"" + modelClass.getName() 
							+ "\": " +  modelField.getName() + " is not supported primitive type yet !!!");
				}
				modelSerializationImpl.addColumn(modelField.getName(), modelField.getType());
			}
		}
		
		if(modelSerializationImpl.getPrimaryKey() != null){
			if(modelSerializationImpl.getPrimaryKey() instanceof Composite
					&& ((Composite) modelSerializationImpl.getPrimaryKey()).getColumns() == null){
				throw new ModelException("\"" + modelClass.getName() 
					+ "\": has a composite primary key but there is no column which is defined inside !!!");
			}
		}else {
			if(modelSerializationImpl.getColumns() == null){
				throw new ModelException("\"" + modelClass.getName() + "\": has no column inside !!!");
			}
		}
		
		return modelSerializationImpl;
	}
	/**
	 * 
	 * @param clss
	 * @param fieldName
	 * @param instance
	 * @return
	 * @throws OmException
	 */
	public static Object invokeGetMethod(Class<?> clss, String fieldName, Object instance) throws OmException {
		try{
			Method method = clss.getMethod(METHOD_GETTER + StringUtils.toUpperFirstLetter(fieldName));
			
			return method.invoke(instance);
		}catch(Exception e){
			throw new UnExpectedException(e);
		}
	}
	/**
	 * 
	 * @param clss
	 * @param fieldName
	 * @param instance
	 * @param args
	 * @return
	 * @throws OmException
	 */
	public static Object invokeSetMethod(Class<?> clss, String fieldName, Object instance, Object args) throws OmException {
		try{
			Method method = clss.getMethod(METHOD_SETTER + StringUtils.toUpperFirstLetter(fieldName), args.getClass());

			return method.invoke(instance, args);
		}catch(Exception e){
			e.printStackTrace();
			throw new UnExpectedException(e);
		} 
	}
}
